home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-06-16 | 12.3 KB | 251 lines | [TEXT/CCL2] |
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;;
- ;;;; Suspending and Resuming Apple Events
- ;;;;
- ;;;; Dan S. Camper
- ;;;;
- ;;;; There are times when you don't want to immediately handle an incoming Apple Event but would
- ;;;; rather defer its reply until you've obtained some further piece of information to stick into the
- ;;;; reply. Normally, Apple Events must be completely handled within one "program loop cycle" (ie,
- ;;;; no functional breaks between the Apple Event manager calling MCL and the population of the
- ;;;; Apple Event reply). However, if you need information that is stored on another system and you
- ;;;; cannot retrieve it within that single function call then you'll have to suspend the Apple Event
- ;;;; to prevent the Apple Event Manager from sending the (dataless) reply anyway. I wrote these
- ;;;; functions for precisely that reason: Data that would be stashed into an Apple Event reply was
- ;;;; being obtained from a VAX via the Comm Toolbox; I had no way of knowing when I would actually
- ;;;; receive the data, and I had to cycle through my main event loop in order to idle the Comm Toolbox
- ;;;; tools and handle other requests.
- ;;;;
- ;;;; MCL bypasses the Apple Event dispatching mechanism and, incidentally, suspends (almost) every single
- ;;;; event it gets anyway. The only events that are not suspended are 1) events that MCL sends to itself,
- ;;;; and 2) events that arrive while MCL is *sending* an event somewhere else. These changes leverage off
- ;;;; that behavior by simply preventing MCL from automatically resuming an Apple Event after the handler
- ;;;; call is completed.
- ;;;;
- ;;;; The basic change is this: If you want to suspend an Apple Event then simply return :suspend from your
- ;;;; Apple Event handler. This return value will prevent MCL from resuming the Apple Event and it will store
- ;;;; the event and its reply onto a separate stack, pending eventual resuming. Before suspending the event
- ;;;; you should obtain the "reference number" for the event (actually just the address of the Apple Event
- ;;;; record) and store it somewhere so you can later retrieve the event records and resume processing.
- ;;;;
- ;;;; Two other changes to MCL's Apple Event handling were also made here: 1) If multiple Apple Events are
- ;;;; internally queued for their initial passing to handlers, only one event at a time is sent off for processing
- ;;;; (previously all the events were called); and 2) the #$keyErrorNumber parameter in the reply record is
- ;;;; set only if the parameter didn't exist before -- this allows your handler to use this parameter without MCL
- ;;;; overriding the value later.
- ;;;;
- ;;;; Since three functions here are initially defined in MCL's kernel then you may want to evaluate these contents
- ;;;; with *warn-if-redefine-kernel* set to nil.
-
- (in-package :ccl)
-
- (export '(with-suspended-appleevent get-appleevent-suspension-ref unsuspend-appleevent
- suspended-appleevent-p resume-and-send-appleevent dispose-suspended-appleevent
- clear-suspended-appleevent))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;
- ;; suspend-appleevent & resume-and-send-applevent code
- ;;
- (defvar *suspended-appleevents* nil)
- (defvar *handling-incoming-event* nil)
-
- ; Useful macro for using previously-suspended events and replies.
- ;
- (defmacro with-suspended-appleevent ((refcon event reply) &body body)
- (let ((item (gensym)))
- `(let ((,event nil)
- (,reply nil))
- (let ((,item (find ,refcon *suspended-appleevents* :test #'= :key #'(lambda (x) (%ptr-to-int (car x))))))
- (if ,item
- (setq ,event (car ,item)
- ,reply (cdr ,item)))
- ,@body))))
-
- ; Function returns the Apple Event's "reference number" -- store this number somewhere so you can retrieve the
- ; event and reply later.
- ;
- (defun get-appleevent-suspension-ref (event)
- (%ptr-to-int event))
-
- ; Function simply removes the AppleEvent and reply from *suspended-appleevents*; it does *not* unsuspend the event.
- ;
- (defun unsuspend-appleevent (refcon)
- (let ((item (find refcon *suspended-appleevents* :test #'= :key #'(lambda (x) (%ptr-to-int (car x))))))
- (when item
- (#_DisposePtr (car item))
- (#_DisposePtr (cdr item))
- (setf *suspended-appleevents* (delete refcon *suspended-appleevents*
- :test #'=
- :key #'(lambda (x) (%ptr-to-int (car x)))))
- (free-cons item))))
-
- (defun suspended-appleevent-p (refcon)
- (if (find refcon *suspended-appleevents* :test #'= :key #'(lambda (x) (%ptr-to-int (car x))))
- t
- nil))
-
- ; Given a reference this event unsuspends an event, sends the reply and clears the event off the
- ; hash table.
- ;
- (defun resume-and-send-appleevent (refcon)
- (let ((item (find refcon *suspended-appleevents* :test #'= :key #'(lambda (x) (%ptr-to-int (car x))))))
- (when item
- (#_AESetTheCurrentEvent (car item))
- (#_AEResumeTheCurrentEvent (car item) (cdr item) (%int-to-ptr #$kAENoDispatch) 0)))
- (unsuspend-appleevent refcon)
- t)
-
- ; Function completely disposes of a suspended AppleEvent, given its reference.
- ;
- (defun dispose-suspended-appleevent (refcon)
- (let ((item (find refcon *suspended-appleevents* :test #'= :key #'(lambda (x) (%ptr-to-int (car x))))))
- (when item
- (without-interrupts
- (#_AEDisposeDesc (car item))
- (#_AEDisposeDesc (cdr item))
- (unsuspend-appleevent refcon))))
- t)
-
- ; Clean up of all suspended AppleEvents
- ;
- (defun clear-suspended-appleevents ()
- (loop while *suspended-appleevents*
- do (dispose-suspended-appleevent (%ptr-to-int (caar *suspended-appleevents*))))
- (setq *suspended-appleevents* nil))
-
- (pushnew #'clear-suspended-appleevents *lisp-cleanup-functions* :key #'function-name)
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;
- ;; mods to highlevel-events module
- ;;
-
- ; Added *handling-incoming-event* setting to ensure that a subsequent call to #'do-deferred-appleevents
- ; doesn't tromp all over this call. I'm not sure if it's really needed, though.
- ;
- (defpascal defer-appleevent-handler (:pointer theAppleEvent :pointer reply
- :long handlerRefcon :word)
- (declare (ignore handlerRefcon))
- (let ((result #$noErr)
- (*handling-incoming-event* t))
- (rlet ((source :word)
- (actualType :long)
- (actualSize :long))
- (if (or *inside-aesend*
- (and (eql #$noErr (#_AEGetAttributePtr theAppleEvent #$keyEventSourceAttr
- #$TypeShortInteger actualType source 2 actualSize))
- (or (eql #$kAESameProcess (%get-word source))
- (eql #$kAEDirectCall (%get-word source)))))
- (setf result (do-appleevent theAppleEvent reply nil))
- (progn
- (ae-error (#_AESuspendTheCurrentEvent theAppleEvent))
- (setq *deferred-appleevents* (nconc *deferred-appleevents*
- (cheap-cons (cheap-cons (copy-record theAppleEvent :AEDesc)
- (copy-record reply :AEDesc))
- nil))))))
- result))
-
- ; Function now processes only the first queued AppleEvent during its call instead of the whole list.
- ; This is a better resource-management scheme for some operations, particularly for servers.
- ;
- (defun do-deferred-appleevents ()
- (when (and *deferred-appleevents*
- (not *doing-deferred-appleevents*)
- (not *handling-incoming-event*))
- (setq *doing-deferred-appleevents* t)
- (unwind-protect
- (let* ((event-info (pop-and-free *deferred-appleevents*))
- (theAppleEvent (car (the list event-info)))
- (reply (cdr (the list event-info))))
- (if (eql :suspend (do-appleevent theAppleEvent reply t))
- (setq *suspended-appleevents* (nconc *suspended-appleevents*
- (cheap-cons (cheap-cons theAppleEvent reply)
- nil))))
- (free-cons event-info))
- (setq *doing-deferred-appleevents* nil))))
-
- ; The the Lisp handler is now expected to return :suspend value if the event was suspended with
- ; #'suspend-appleevent; if this value is found then the event and reply records are *not* disposed.
- ; Also, install a #$keyErrorNumber parameter after the Lisp handler only if there is not value already
- ; present in the reply.
- ;
- (defun do-appleevent (theAppleEvent reply deferred-p)
- (let ((result #$noErr)
- (handler-result nil)
- (class nil)
- (id nil))
- (block buck-stops-here
- (unwind-protect ; don't let anyone throw past me!!
- (handler-case
- (flet ((no-handler ()
- (error (make-condition 'appleevent-error :oserr #$errAEEventNotHandled
- :error-string (format nil "No Lisp Handler for '~a' '~a'"
- class id)))))
- (setq class (ae-get-attribute-type theAppleEvent #$keyEventClassAttr)
- id (ae-get-attribute-type theAppleEvent #$keyEventIDAttr))
- (let ((id-table (gethash class %appleevent-handlers%)))
- (unless id-table
- (setq id-table (gethash :|****| %appleevent-handlers%))
- (unless id-table (no-handler)))
- (let ((handler (gethash id id-table)))
- (unless handler
- (no-handler))
- (setf handler-result (funcall (car handler) *application* theAppleEvent reply (cdr handler))))))
- (error (c)
- (when *report-appleevent-errors*
- (format *error-output* "~%> Error while handling AppleEvent: '~a' '~a'~%> "
- class id)
- (report-condition c *error-output*))
- ; try to put the error string in the reply (the reply may be null)
- ; if the event is itself a reply!
- (ae-put-parameter-char theAppleEvent #$keyErrorString
- (with-output-to-string (s)
- (report-condition c s))
- nil)
- (if (typep c 'appleevent-error)
- (setq result (oserr c)) ; return the error to the AppleEvent Manager
- (setq result #$errAEEventNotHandled))))
- (return-from buck-stops-here)))
- ; try to put the result code in the reply (the reply may be null)
- ; if the event is itself a reply!
- (unless (or (eql handler-result :suspend)
- (ae-get-parameter-longinteger reply #$keyErrorNumber nil)
- (neq result #$noErr))
- (ae-put-parameter-longinteger reply #$keyErrorNumber result nil))
- (when deferred-p
- (unless (eql handler-result :suspend)
- (#_AESetTheCurrentEvent theAppleEvent)
- (#_AEResumeTheCurrentEvent theAppleEvent reply (%int-to-ptr #$kAENoDispatch) 0)
- (#_DisposePtr theAppleEvent)
- (#_DisposePtr reply))
- (when *appleevent-quit*
- (setq *appleevent-quit* nil) ; don't repeat if aborted out
- (quit)))
- (if (eql handler-result :suspend)
- handler-result
- result)))
-
- #|
-
- ; Example of handler that suspends event and replies later. Send the test Apple Event to MCL
- ; using HyperCard 2.1 or later with the "ask program" or "answer program" HyperTalk commands.
- ; HyperCard should send a Lisp expression -- eg, "(* 9 3)" -- for evaluation.
-
- (defmethod suspended-eval-handler ((a application) event reply refcon)
- (declare (ignore reply refcon))
- (let ((ae-ref (get-appleevent-suspension-ref event)))
- (eval-enqueue (list 'handle-suspended-event ae-ref))) ; Can't do this without the suspend code!
- :suspend)
-
- (defun handle-suspended-event (ae-ref)
- (with-suspended-appleevent (ae-ref event reply)
- (let ((what (ignore-errors (ccl::ae-get-parameter-char event #$keyDirectObject nil))))
- (if what
- (ccl::ae-put-parameter-char reply #$keyDirectObject (write-to-string (eval (read-from-string what)))))))
- (resume-and-send-appleevent ae-ref))
-
- (install-appleevent-handler :|misc| :|dosc| #'suspended-eval-handler)
- (install-appleevent-handler :|misc| :|eval| #'suspended-eval-handler)
-
- |#